/**
 * @file 内部form-item hoc
 * @author liyong18@baidu.com
 *
 * https://github.com/JedWatson/react-select
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import {Renderer, filter, tokenize} from '../renderer';
import {resolveVariable, evalExpression2} from '../util';
import {Mixin as FormsyMixin} from 'formsy-react';
import {decorate as Mixin} from 'react-mixin';
import validationErrors from './validation-messages';
import * as assign from 'object-assign';
import * as cx from 'classnames';
import * as pick from 'lodash/pick';
import * as omit from 'lodash/omit';
import * as isEqual from 'lodash/isEqual';
import FetchSource from '../hoc/fetch-source';
import Html from '../../widgets/html';

import {
  FormGroup,
  ControlLabel,
  HelpBlock
} from 'react-bootstrap';
import OnExpression from '../hoc/on-expression';
export const Enhance = (itemConfig) => ComposedComponent =>
{
  @Renderer({
    name: itemConfig.name || `form-item-${itemConfig.type}`,
    desc: itemConfig.desc || 'form-item-' + itemConfig.type,
    test: itemConfig.test || ((path, config) => {
      return /(?:^|\/)form-item$/.test(path) && config.type === itemConfig.type
    }),
    filter: (config, path) => {
      const result = itemConfig.filter && itemConfig.filter(config, path);
      return assign({}, config, result, {
        validationErrors: assign({}, validationErrors, result && result.validationErrors, config.validationErrors)
      });
    },
    schema: itemConfig.schema,
    validateSchema: itemConfig.validateSchema
  })
  @FetchSource()
  @OnExpression
  @Mixin(FormsyMixin)
  class FormItem extends PureComponent {

    // 默认属性
    static propTypes = {
      className: PropTypes.string,
      formMode: PropTypes.string,
      formHorizontal: PropTypes.object,
      inline: PropTypes.bool,

      // 是否展示label(建议只在hoc中定义)
      haveLabel: PropTypes.bool,

      // 是否展示错误(建议只在hoc中定义)
      haveError: PropTypes.bool,

      // 是否自动拉取数据(建议只在hoc中定义)
      haveFetch: PropTypes.bool,

      // 是否展示远程错误(建议只在hoc中定义)
      haveFetchError: PropTypes.bool,

      // 是否展示除了本组件的其它组件(建议只在hoc中定义)
      haveAllElements: PropTypes.bool,

      // 是否需要包裹的element
      haveWrapComponent: PropTypes.bool,

      // 影藏 label 当没有设置的时候。
      hideLabelByDefault: PropTypes.bool,

      // 是否增加到表单中(建议只在hoc中定义)
      attachForm: PropTypes.bool,

      // 校验的属性(hoc和schema中都可以定义)
      validations: PropTypes.string,

      // value的template
      valueTemplate: PropTypes.string,

      // 哪些属性改变了会影响

      detectValuePropName: PropTypes.string
    };


    static defaultProps = assign({
        haveLabel: true,
        haveError: true,
        haveFetch: true,
        haveFetchError: true,
        haveAllElements: true,
        haveWrapComponent: true,
        hideLabelByDefault: false,
        name: '',
        desc: '',
        valueWatch: '',
        valueTemplate: '',
        detectValuePropName: 'data',
        attachForm: true,
        validations: ''
      }, omit(itemConfig, ['validate', 'type', 'schema', 'validateSchema', 'filter', 'test', 'desc', 'name'])
    );

    input = null;

    constructor(props) {
      super(props);

      this.setValidations = this.setValidations.bind(this);
      this.extendValidations = this.extendValidations.bind(this);
      this.setValue = this.setValue.bind(this);
      this.resetValue = this.resetValue.bind(this);
      this.getValue = this.getValue.bind(this);
      this.hasValue = this.hasValue.bind(this);
      this.submitForm = this.submitForm.bind(this);
      this.getErrorMessage = this.getErrorMessage.bind(this);
      this.getErrorMessages = this.getErrorMessages.bind(this);
      this.isFormDisabled = this.isFormDisabled.bind(this);
      this.isValid = this.isValid.bind(this);
      this.isPristine = this.isPristine.bind(this);
      this.isFormSubmitted = this.isFormSubmitted.bind(this);
      this.isRequired = this.isRequired.bind(this);
      this.showRequired = this.showRequired.bind(this);
      this.showError = this.showError.bind(this);
      this.isValidValue = this.isValidValue.bind(this);
      this.validateComponent = this.validateComponent.bind(this);

      // hoc中可以传validate属性
      if (itemConfig.validate) {
        this.extendValidations(itemConfig.validate);
      }

      this.state = {
        initialSourceFetched: false, // 标记是否初始 source 拉取了
        sourceLocalData: null
      };
    }

    componentDidMount() {
      const {
        source,
        fetchSource,
        data,
        locals,
        haveFetch,
        isReady,
        valueTemplate
      } = this.props;

      if (haveFetch && source && (typeof isReady === 'undefined' || isReady)) {
        if (typeof source=== 'string' && /^\$/.test(source)) {
          let sourceLocalData = resolveVariable(source, data) || resolveVariable(source, locals);
          this.setState({
            sourceLocalData,
            initialSourceFetched: true
          });
        } else {
          this.setState({
            initialSourceFetched: true
          }, () => fetchSource(data));
        }
      }

      if (valueTemplate) {
        const value = evalExpression2(valueTemplate, data, this.props.locals);
        if (!(typeof value === 'number' && isNaN(value))) {
          this.setValue(value);
        }
      }

      //haveFetch && source && (typeof isReady === 'undefined' || isReady) && ;


      /*if (typeof value === 'string' && value.indexOf('$')) {
        this.setValue(tokenize(value, data));
      }*/
    }

    componentWillReceiveProps(nextProps) {
      const props = this.props;

      if (
        !this.state.initialSourceFetched
        && nextProps.haveFetch
        && nextProps.source
        && props.data !== nextProps.data
        && nextProps.isReady
      ) {

        if(/^\$/.test(nextProps.source)) {
          let sourceLocalData = resolveVariable(nextProps.source, nextProps.data) || resolveVariable(nextProps.source, nextProps.locals);
          this.setState({
            sourceLocalData,
            initialSourceFetched: true
          });
        } else {
          this.setState({
            initialSourceFetched: true
          }, () => nextProps.fetchSource(nextProps.data));
        }
      } else if (nextProps.source && /\$|<%/.test(nextProps.source) && (props.locals !== nextProps.locals || props.data !== nextProps.data)) {
        if (/^\$/.test(nextProps.source)) {
          const sourceLocalData = resolveVariable(nextProps.source, nextProps.data) || resolveVariable(nextProps.source, nextProps.locals);
          this.setState({
            sourceLocalData
          });
        } else {
          const prevSource = filter(props.source, props.data);
          const nextSource = filter(nextProps.source, nextProps.data);

          if (prevSource !== nextSource) {
            // source 值发生变化 重新拉取。
            this.setValue('');
            nextProps.fetchSource(nextProps.data);
          }
        }
      } else if (
        props.source && props.source.url && /\$|<%/.test(props.source.url)
        || nextProps.source && nextProps.source.url && /\$|<%/.test(nextProps.source.url)
      ) {
        const prevSource = filter(props.source.url, props.data);
        const nextSource = filter(nextProps.source.url, nextProps.data);

        if (prevSource !== nextSource) {
          // source 值发生变化 重新拉取。
          this.setValue('');
          nextProps.fetchSource(nextProps.data);
        }
      }

      const prevData = this.props[nextProps.detectValuePropName];
      const nextData = nextProps[nextProps.detectValuePropName];

      if (nextProps.valueWatch !== false
        && nextProps.valueTemplate
        && prevData !== nextData
        && this.calIfChange(prevData, nextData, nextProps.valueWatch)) {
        const prevValue = evalExpression2(nextProps.valueTemplate, prevData, this.props.locals);
        const nextValue = evalExpression2(nextProps.valueTemplate, nextData, this.props.locals);
        if (!(typeof nextValue === 'number' && isNaN(nextValue)) && !isEqual(prevValue, nextValue)) {
          this.setValue(nextValue);
        }
      }
    }

    calIfChange(prevData, nextData, valueWatch) {
      if (valueWatch === '') {
        return true;
      }

      const valueWitchs = valueWatch.split(',');

      return valueWitchs.some(v => resolveVariable(v.trim(), prevData) !== resolveVariable(v, nextData));
    }

    reload() {
      const input = this.input;
      const {
        haveFetch,
        source,
        data,
        fetchSource
      } = this.props;

      const props = this.props;

      if (input && input.reload) {
        input.reload();
      } else if (haveFetch && source) {
        this.setValue('');
        fetchSource(data);
      }

      if (props.valueTemplate) {
        const vdata = props[props.detectValuePropName];
        let value = evalExpression2(props.valueTemplate, vdata, props.locals);
        value = value === 0 ? value : value ? value : '';

        this.setValue(value);
      }
    }

    focus() {
      this.input && this.input.focus && this.input.focus();
    }

    validateComponent() {
      return !this.context.formsy || this.context.formsy.validate(this);
    }

    render() {
      const {
        label,
        noOffset,
        desc,
        formMode,
        formHorizontal,
        className,
        inline,
        inlineLabelClassName,
        haveAllElements,
        haveLabel,
        haveError,
        hideLabelByDefault,
        haveFetchError,
        sourceError,
        sourceFetching,
        type,
        inputClassName,
        labelClassName,
        data,
        locals,
        wrappedComponent,
        wrappedProps,
        haveWrapComponent,
        inputWrapClassName
      } = this.props;

      if (type === 'hidden') {
        return null;
      }
      const WrappedComponent = wrappedComponent;

      const isRequired = this.isRequired();

      const formItemProps = pick(this, [
        'setValidations',
        'setValue',
        'resetValue',
        'getValue',
        'hasValue',
        'submitForm',
        'getErrorMessage',
        'getErrorMessages',
        'isFormDisabled',
        'isValid',
        'isPristine',
        'isFormSubmitted',
        'isRequired',
        'showRequired',
        'showError',
        'isValidValue',
        'setValidState',
        'extendValidations',
        'validateComponent'
      ]);

      formItemProps.sourceLocalData = this.state.sourceLocalData;
      formItemProps.formItemValue = this.getValue();

      if (!haveAllElements || !haveLabel && !haveError && !haveFetchError) {
        let element = (
          <ComposedComponent
            ref={ins => this.input = ins}
            {...this.props}
            {...formItemProps}
          />
        );
        if (WrappedComponent && haveWrapComponent) {
          element =  <WrappedComponent {...wrappedProps}>{element}</WrappedComponent>;
        }
        return element;
      }
      const errorMessage = this.getErrorMessage();
      const showError = /*!this.isPristine() && */(this.showError() || this.isFormSubmitted() && errorMessage);
      let input = [];
      if (sourceError && haveFetchError) {
        input.push(
          <p key="message" className="alert alert-danger">{sourceError.message}</p>
        );
      } else if (sourceFetching && haveFetchError) {
        input.push(
          <i key="loading" className="fa fa-spinner fa-spin fa-2x fa-fw" />
        )
      }
      input.push(
        <ComposedComponent
          {...this.props}
          {...formItemProps}
          ref={ins => this.input = ins}
          inputClassName={cx(inputClassName, {invisible: (sourceFetching || sourceError) && haveFetchError})}
          key="form-item"
        />
      );

      let body = [
        input,
        haveError && (showError || desc) ? (<HelpBlock key="help">{showError ? (errorMessage || '表单项验证错误，请仔细检查') : (desc ? (<Html html={filter(desc, data)} />) : null)}</HelpBlock>) : null
      ];

      if (formMode === 'horizontal' && !inline) {
        body = (
          <div className={label === false || noOffset || hideLabelByDefault && !label ? 'col-xs-12' : cx(formHorizontal.right,  label && haveLabel ? '' : formHorizontal.offset, inputWrapClassName)}>
            {body}
          </div>
        );
      }
      if (formMode === 'normal' && !inline) {
        body = (
          <div>
            {body}
          </div>
        );
      }

      if(inline /*&& formMode === 'horizontal'*/) {
        body = (
          <div className="inline-form-control">
            {body}
          </div>
        )
      }

      body = (
        <div className={cx(inline ? 'form-group-inline' : 'form-group', `form-item form-item-${type}`, className, showError ? `has-error` : null)}
        >
          {haveLabel && label ? (<ControlLabel className={cx(labelClassName, inline && inlineLabelClassName || cx(formMode === 'horizontal' ? formHorizontal.left : ''))}>{filter(label, data, locals)}{isRequired && <span className="required">*</span>}</ControlLabel>) : null}
          {body}
        </div>
      );

      if (WrappedComponent && haveWrapComponent) {
        body = <WrappedComponent {...wrappedProps}>{body}</WrappedComponent>;
      }

      return body;
    }
  }
  return FormItem;
}

export default Enhance;
